// Copyright (c) 2009, Michael Patraw
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//     * The name of Michael Patraw may not be used to endorse or promote 
//       products derived from this software without specific prior written
//       permission.
//
// THIS SOFTWARE IS PROVIDED BY Michael Patraw ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL Michael Patraw BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <cassert>
#include <cstdlib>
#include <ctime>
#include <string>

#include "animals.h"
#include "MersenneTwister.h"

void Animal::move(int dx, int dy)
{
    coord_.setX(coord_.getX() + dx);
    coord_.setY(coord_.getY() + dy);
}

//==============================================================================
//==============================================================================

const int START_ENERGY    = 1000;
const int TIRED_LEVEL     = 700;
const int ENE_DYING_LEVEL = 100;

const int START_SATIATION = 2000;
const int HUNGRY_LEVEL    = 1800;
const int STARVING_LEVEL  = 1000;
const int SAT_DYING_LEVEL = 500;

const int START_HYDRATION   = 1000;
const int THIRSTY_LEVEL     = 900;
const int DEHYDRATION_LEVEL = 400;
const int HYD_DYING_LEVEL   = 100;

const int HYPOTHERMIA_LEVEL = -10;
const int COLD_LEVEL        = -3;
const int HOT_LEVEL         = 3;
const int HEATSTROKE_LEVEL  = 10;

const int HYPO_DEAD_LEVEL   = -200;
const int HEAT_DEAD_LEVEL   = 200;

Human::Human(const char * name) :
Animal(Glyph('@', White), Coord(0, 0)), name_(name),
warmth_(0), temptimer_(0),
energy_(START_ENERGY),
satiation_(Stat(START_SATIATION)),
hydration_(Stat(START_HYDRATION)),
condition_(0),
inv_(INVENTORY_SIZE),
hand_(NULL), head_(NULL), body_(NULL), legs_(NULL), feet_(NULL),
deathreason_("nothing")
{
    // Set stats. TODO: Possibly add profession specific stats.
    health_.setMax(10);
    health_.maxOut();
    attack_.setMax(4);
    attack_.maxOut();
    defense_.setMax(3);
    defense_.maxOut();
}

Human::~Human()
{
    // Double deallocation shouldn't happen.
    if (hand_)
        delete hand_;
    if (head_)
        delete head_;
    if (body_)
        delete body_;
    if (legs_)
        delete legs_;
    if (feet_)
        delete feet_;
}

void Human::kill(const char * reason)
{
    addCondition(COND_DEAD);
    deathreason_ = reason;
}

const char * Human::getName() const
{
    return name_;
}

const char * Human::getDeathReason() const
{
    return deathreason_;
}

unsigned int Human::getCondition()
{
    return condition_;
}

const char * Human::getConditionNames() const
{
    std::string str = "";
    if (condition_ & COND_DYING) {
        if (str != "")
            str += ", ";
        str += "dying";
    }
    if (condition_ & COND_TIRED) {
        if (str != "")
            str += ", ";
        str += "tired";
    }
    if (condition_ & COND_HUNGRY) {
        if (str != "")
            str += ", ";
        str += "hungry";
    }
    if (condition_ & COND_STARVING) {
        if (str != "")
            str += ", ";
        str += "starving";
    }
    if (condition_ & COND_THIRSTY) {
        if (str != "")
            str += ", ";
        str += "thirsty";
    }
    if (condition_ & COND_DEHYDRATED) {
        if (str != "")
            str += ", ";
        str += "dehydrated";
    }
    if (condition_ & COND_COLD) {
        if (str != "")
            str += ", ";
        str += "cold";
    }
    if (condition_ & COND_HYPOTHERMIA) {
        if (str != "")
            str += ", ";
        str += "hypothermia";
    }
    if (condition_ & COND_HOT) {
        if (str != "")
            str += ", ";
        str += "hot";
    }
    if (condition_ & COND_HEATSTROKE) {
        if (str != "")
            str += ", ";
        str += "heatstroke";
    }
    // Dead
    if (condition_ & COND_DEAD) {
        str = "dead";
    }
    
    return str.c_str();
}

bool Human::hasCondition(unsigned int cond)
{
    return condition_ & cond;
}

void Human::addCondition(unsigned int cond)
{
    if (!(condition_ & cond))
        condition_ ^= cond;
}

void Human::remCondition(unsigned int cond)
{
    if (condition_ & cond)
        condition_ ^= cond;
}
    
int Human::getWarmth() const
{
    return warmth_;
}

int Human::getEnergy() const
{
    return energy_;
}

int Human::getSatiation()
{
    return satiation_.current();
}

int Human::getHydration()
{
    return hydration_.current();
}

void Human::setWarmth(int w)
{
    warmth_ = w + getTotalItemWarmth();
}

void Human::setEnergy(int e)
{
    energy_ = e;
}

void Human::setSatiation(int s)
{
    satiation_.current() = s;
}

void Human::setHydration(int h)
{
    hydration_.current() = h;
}

void Human::modWarmth(int dw)
{
    warmth_ += dw;
}

void Human::modEnergy(int de)
{
    energy_ += de;
}

void Human::modSatiation(int ds)
{
    satiation_.current() += ds;
}

void Human::modHydration(int dh)
{
    hydration_.current() += dh;
}

void Human::update()
{
    _updateConditions();
    
    // Effects from temperature.
    int de = 1, dh = 1;
    if (hasCondition(COND_COLD)) {
        de = 2;
        temptimer_ -= de;
    }
    else if (hasCondition(COND_HYPOTHERMIA)) {
        de = 3;
        temptimer_ -= de;
    }
    if (hasCondition(COND_HOT)) {
        dh = 2;
        temptimer_ += dh;
    }
    else if (hasCondition(COND_HEATSTROKE)) {
        dh = 3;
        temptimer_ += dh;
    }
    else {
        // Condition Stabalized.
        if (temptimer_ < 0)
            temptimer_++;
        else if (temptimer_ > 0)
            temptimer_--;
    }
        
    energy_ -= de;
    satiation_.current()--;
    hydration_.current() -= dh;
}

//==============================================================================

Item * Human::equip(Item * item)
{
    if (item == NULL)
        return NULL;
    
    Item * ret = item;

    switch (item->getId()) {
        case ID_NONE:
            return ret;
            break;
        case ID_HAND:
            if (getHand()) { // Replace item.
                ret = remHand();
                setHand(item);
            }
            else {
                setHand(item);
                ret = NULL;
            }
            break;
        case ID_HEAD:
            if (getHead()) { // Replace item.
                ret = remHead();
                setHead(item);
            }
            else {
                setHead(item);
                ret = NULL;
            }
            break;
        case ID_BODY:
            if (getBody()) { // Replace item.
                ret = remBody();
                setBody(item);
            }
            else {
                setBody(item);
                ret = NULL;
            }
            break;
        case ID_LEGS:
            if (getLegs()) { // Replace item.
                ret = remLegs();
                setLegs(item);
            }
            else {
                setLegs(item);
                ret = NULL;
            }
            break;
        case ID_FEET:
            if (getFeet()) { // Replace item.
                ret = remFeet();
                setFeet(item);
            }
            else {
                setFeet(item);
                ret = NULL;
            }
            break;
        default:
            break;
    }
    
    return ret;
}

ItemList * Human::getInv()
{
    return &inv_;
}

Item * Human::getHand()
{
    return hand_;
}

Item * Human::getHead()
{
    return head_;
}

Item * Human::getBody()
{
    return body_;
}

Item * Human::getLegs()
{
    return legs_;
}

Item * Human::getFeet()
{
    return feet_;
}

Item * Human::remHand()
{
    Item * temp = hand_;
    hand_ = NULL;
    _updateStats();
    return temp;
}

Item * Human::remHead()
{
    Item * temp = head_;
    head_ = NULL;
    _updateStats();
    return temp;
}

Item * Human::remBody()
{
    Item * temp = body_;
    body_ = NULL;
    _updateStats();
    return temp;
}

Item * Human::remLegs()
{
    Item * temp = legs_;
    legs_ = NULL;
    _updateStats();
    return temp;
}

Item * Human::remFeet()
{
    Item * temp = feet_;
    feet_ = NULL;
    _updateStats();
    return temp;
}

bool Human::setHand(Item * item)
{
    if (hand_)
        return false;
    hand_ = item;
    _updateStats();
    return true;
}

bool Human::setHead(Item * item)
{
    if (head_)
        return false;
    head_ = item;
    _updateStats();
    return true;
}

bool Human::setBody(Item * item)
{
    if (body_)
        return false;
    body_ = item;
    _updateStats();
    return true;
}

bool Human::setLegs(Item * item)
{
    if (legs_)
        return false;
    legs_ = item;
    _updateStats();
    return true;
}

bool Human::setFeet(Item * item)
{
    if (hand_)
        return false;
    feet_ = item;
    _updateStats();
    return true;
}

int Human::getTotalItemHealth() const
{
    int total = 0;
    if (hand_)
        total += hand_->getHealth();
    if (head_)
        total += head_->getHealth();
    if (body_)
        total += body_->getHealth();
    if (legs_)
        total += legs_->getHealth();
    if (feet_)
        total += feet_->getHealth();
    return total;
}

int Human::getTotalItemAttack() const
{
    int total = 0;
    if (hand_)
        total += hand_->getAttack();
    if (head_)
        total += head_->getAttack();
    if (body_)
        total += body_->getAttack();
    if (legs_)
        total += legs_->getAttack();
    if (feet_)
        total += feet_->getAttack();
    return total;
}

int Human::getTotalItemDefense() const
{
    int total = 0;
    if (hand_)
        total += hand_->getDefense();
    if (head_)
        total += head_->getDefense();
    if (body_)
        total += body_->getDefense();
    if (legs_)
        total += legs_->getDefense();
    if (feet_)
        total += feet_->getDefense();
    return total;
}

int Human::getTotalItemWarmth() const
{
    int total = 0;
    if (hand_)
        total += hand_->getWarmth();
    if (head_)
        total += head_->getWarmth();
    if (body_)
        total += body_->getWarmth();
    if (legs_)
        total += legs_->getWarmth();
    if (feet_)
        total += feet_->getWarmth();
    return total;
}

void Human::_updateStats()
{
    // TODO: Updates stats based on conditions.
    // Max is used as a base line.
    
    int wounds = 0;
    if (health_.current() < health_.max())
        wounds = health_.max() - health_.current(); // Save damage.
    health_.maxOut();
    health_.current() += (getTotalItemHealth() - wounds);
    
    attack_.maxOut();
    attack_.current() += getTotalItemAttack();
    
    defense_.maxOut();
    defense_.current() += getTotalItemDefense();
}

void Human::_updateConditions()
{
    if (hasCondition(COND_DEAD)) {
        return;
    }
    condition_ = 0;
    
    // Energy.
    if (energy_ <= 0) {
        addCondition(COND_DEAD);
        deathreason_ = "over-exertion";
    }
    else if (energy_ <= ENE_DYING_LEVEL) {
        addCondition(COND_DYING);
    }
    else if (energy_ <= TIRED_LEVEL) {
        addCondition(COND_TIRED);
    }
    else if (energy_ > START_ENERGY) {
        energy_ = START_ENERGY;
    }
    
    // Satiation.
    if (satiation_.current() <= 0) {
        addCondition(COND_DEAD);
        deathreason_ = "starvation";
    }
    else if (satiation_.current() <= SAT_DYING_LEVEL) {
        addCondition(COND_DYING);
    }
    else if (satiation_.current() <= STARVING_LEVEL) {
        addCondition(COND_STARVING);
    }
    else if (satiation_.current() <= HUNGRY_LEVEL) {
        addCondition(COND_HUNGRY);
    }
    else if (satiation_.current() > START_SATIATION) {
        satiation_.current() = START_SATIATION;
    }
    
    // Hydration.
    if (hydration_.current() <= 0) {
        addCondition(COND_DEAD);
        deathreason_ = "dehydration";
    }
    else if (hydration_.current() <= HYD_DYING_LEVEL) {
        addCondition(COND_DYING);
    }
    else if (hydration_.current() <= DEHYDRATION_LEVEL) {
        addCondition(COND_DEHYDRATED);
    }
    else if (hydration_.current() <= THIRSTY_LEVEL) {
        addCondition(COND_THIRSTY);
    }
    else if (hydration_.current() > START_HYDRATION) {
        addCondition(COND_DEAD);
        deathreason_ = "water poisoning";
    }
    
    // Temperature.
    if (warmth_ <= HYPOTHERMIA_LEVEL) {
        addCondition(COND_HYPOTHERMIA);
    }
    else if (warmth_ <= COLD_LEVEL) {
        addCondition(COND_COLD);
    }
    else if (warmth_ >= HOT_LEVEL && warmth_ < HEATSTROKE_LEVEL) {
        addCondition(COND_HOT);
    }
    else if (warmth_ > HEATSTROKE_LEVEL) {
        addCondition(COND_HEATSTROKE);
    }
    
    if (temptimer_ <= HYPO_DEAD_LEVEL) {
        addCondition(COND_DEAD);
        deathreason_ = "hypothermia";
    }
    else if (temptimer_ >= HEAT_DEAD_LEVEL) {
        addCondition(COND_DEAD);
        deathreason_ = "heatstroke";
    }
    
    if (health()->isZero()) {
        addCondition(COND_DEAD);
        deathreason_ = "poor health";
    }
    
    _updateStats();
}

//==============================================================================
//==============================================================================

void Attack(Animal & attacker, Animal & defender)
{
    MTRand rand(time(NULL));
    
    // (1 to attack + defense) - defense
    int damage = (rand.randInt(
        attacker.attack()->current() +
        defender.defense()->current() -
        1
    ) + 1) - defender.defense()->current();
    
    if (damage >= 1)
        defender.health()->current() -= damage;
}
